package org.xiaoxin.onecoupon.settlement.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import io.netty.util.concurrent.CompleteFuture;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.xiaoxin.onecoupon.framework.config.RedisDistributedProperties;
import org.xiaoxin.onecoupon.framework.exception.ClientException;
import org.xiaoxin.onecoupon.settlement.common.constant.EngineRedisConstant;
import org.xiaoxin.onecoupon.settlement.common.context.UserContext;
import org.xiaoxin.onecoupon.settlement.dto.req.QueryCouponGoodsReqDTO;
import org.xiaoxin.onecoupon.settlement.dto.req.QueryCouponsReqDTO;
import org.xiaoxin.onecoupon.settlement.dto.resp.CouponTemplateQueryRespDTO;
import org.xiaoxin.onecoupon.settlement.dto.resp.QueryCouponsDetailRespDTO;
import org.xiaoxin.onecoupon.settlement.dto.resp.QueryCouponsRespDTO;
import org.xiaoxin.onecoupon.settlement.service.CouponQueryService;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.xiaoxin.onecoupon.settlement.common.constant.EngineRedisConstant.COUPON_TEMPLATE_KEY;
import static org.xiaoxin.onecoupon.settlement.common.constant.EngineRedisConstant.USER_COUPON_TEMPLATE_LIST_KEY;

/**
 * 查询用户可用优惠券列表接口
 * @author xiaoxin
 * 2025/5/15
 */
@Service
@RequiredArgsConstructor
public class CouponQueryServiceImpl implements CouponQueryService {
   private  final StringRedisTemplate stringRedisTemplate;
   private  final RedisDistributedProperties redisDistributedProperties;
   // 在我们本次的业务场景中，属于是 CPU 密集型任务，设置 CPU 的核心数即可
   private final ExecutorService executorService = new ThreadPoolExecutor(
           Runtime.getRuntime().availableProcessors(),
           Runtime.getRuntime().availableProcessors(),
           9999,
           TimeUnit.SECONDS,
           new SynchronousQueue<>(),
           new ThreadPoolExecutor.CallerRunsPolicy()
   );
   @Override
   public QueryCouponsRespDTO listQueryUserCoupons(QueryCouponsReqDTO requestParam) {
      //1.从redis中查询到用户优惠劵
      Set<String> rangeUserCoupons = stringRedisTemplate.opsForZSet().range(String.format(USER_COUPON_TEMPLATE_LIST_KEY, UserContext.getUserId()), 0, -1);
      List<String> couponTemplateIds = rangeUserCoupons.stream()
              .map(each -> StrUtil.split(each, "_").get(0)) //取出couponTempalteID
              .map(each -> String.format(COUPON_TEMPLATE_KEY, each)) //构建COUPON_TEMPLATE_KEY
              .toList();
      //2.从 Redis 中批量获取优惠券模板的详细信息
      List<Object> couponTemplateList = stringRedisTemplate.executePipelined((RedisCallback<String>) connection -> {
         couponTemplateIds.forEach(each -> connection.hashCommands().hGetAll(each.getBytes()));
         return null;
      });
      //3将couponTemplate根据是否有goods分为两类
      List<CouponTemplateQueryRespDTO> couponTemplateDTOList = JSON.parseArray(JSON.toJSONString(couponTemplateList), CouponTemplateQueryRespDTO.class);
      Map<Boolean, List<CouponTemplateQueryRespDTO>> partitioned = couponTemplateDTOList.stream()
              .collect(Collectors.partitioningBy(coupon -> StrUtil.isEmpty(coupon.getGoods())));
      //goods为空的列表
      List<CouponTemplateQueryRespDTO> goodsEmptyList = partitioned.get(true);
      List<CouponTemplateQueryRespDTO> goodsNotEmptyList = partitioned.get(false);
      // 针对当前订单可用/不可用的优惠券列表
      List<QueryCouponsDetailRespDTO> availableCouponList = new ArrayList<>();
      List<QueryCouponsDetailRespDTO> notAvailableCouponList = new ArrayList<>();
      //4.计算折扣金额
      goodsEmptyList.forEach(each->{
         JSONObject jsonObject = JSON.parseObject(each.getConsumeRule());
         QueryCouponsDetailRespDTO resultQueryCouponDetail = BeanUtil.toBean(each, QueryCouponsDetailRespDTO.class);
         BigDecimal maximumDiscountAmount = jsonObject.getBigDecimal("maximumDiscountAmount");
         switch (each.getType()){
            case 0: //立减券
               resultQueryCouponDetail.setCouponAmount(maximumDiscountAmount);
               availableCouponList.add(resultQueryCouponDetail);
               break;
            case 1: //满减券
            if (requestParam.getOrderAmount().compareTo(jsonObject.getBigDecimal("termsOfUse"))>=0){
               resultQueryCouponDetail.setCouponAmount(maximumDiscountAmount);
               availableCouponList.add(resultQueryCouponDetail);
            }else {
               notAvailableCouponList.add(resultQueryCouponDetail);
            }
            break;
            case 2: //折扣券
               if (requestParam.getOrderAmount().compareTo(jsonObject.getBigDecimal("termsOfUse"))>=0){
                  BigDecimal multiply = requestParam.getOrderAmount().multiply(jsonObject.getBigDecimal("discountRate"));
                  if (multiply.compareTo(maximumDiscountAmount)>=0){
                     resultQueryCouponDetail.setCouponAmount(multiply);
                  }else {
                     resultQueryCouponDetail.setCouponAmount(maximumDiscountAmount);
                  }
                  availableCouponList.add(resultQueryCouponDetail);
               }else {
                  notAvailableCouponList.add(resultQueryCouponDetail);
               }
               break;
            default:
               throw  new ClientException("无效的优惠券类型");
         }
      });
      Map<String, QueryCouponGoodsReqDTO> goodsRequestMap = requestParam.getGoodsList().stream()
              .collect(Collectors.toMap(QueryCouponGoodsReqDTO::getGoodsNumber, Function.identity(), (existing, replacement) -> existing));
      goodsNotEmptyList.forEach(each->{
         QueryCouponGoodsReqDTO couponGoods = goodsRequestMap.get(each.getGoods());
         if (couponGoods==null){
            notAvailableCouponList.add(BeanUtil.toBean(each, QueryCouponsDetailRespDTO.class));
         }
         JSONObject jsonObject = JSON.parseObject(each.getConsumeRule());
         QueryCouponsDetailRespDTO resultQueryCouponDetail = BeanUtil.toBean(each, QueryCouponsDetailRespDTO.class);
         switch (each.getType()) {
            case 0: // 立减券
               resultQueryCouponDetail.setCouponAmount(jsonObject.getBigDecimal("maximumDiscountAmount"));
               availableCouponList.add(resultQueryCouponDetail);
               break;
            case 1: // 满减券
               // goodsAmount 大于或等于 termsOfUse
               if (couponGoods.getGoodsAmount().compareTo(jsonObject.getBigDecimal("termsOfUse")) >= 0) {
                  resultQueryCouponDetail.setCouponAmount(jsonObject.getBigDecimal("maximumDiscountAmount"));
                  availableCouponList.add(resultQueryCouponDetail);
               } else {
                  notAvailableCouponList.add(resultQueryCouponDetail);
               }
               break;
            case 2: // 折扣券
               // goodsAmount 大于或等于 termsOfUse
               if (couponGoods.getGoodsAmount().compareTo(jsonObject.getBigDecimal("termsOfUse")) >= 0) {
                  BigDecimal discountRate = jsonObject.getBigDecimal("discountRate");
                  resultQueryCouponDetail.setCouponAmount(couponGoods.getGoodsAmount().multiply(discountRate));
                  availableCouponList.add(resultQueryCouponDetail);
               } else {
                  notAvailableCouponList.add(resultQueryCouponDetail);
               }
               break;
            default:
               throw new ClientException("无效的优惠券类型");
         }
      });
      // 与业内标准一致，按最终优惠力度从大到小排序
      availableCouponList.sort((c1, c2) -> c2.getCouponAmount().compareTo(c1.getCouponAmount()));
      return QueryCouponsRespDTO.builder()
              .availableCouponList(availableCouponList)
              .notAvailableCouponList(notAvailableCouponList)
              .build();


   }

   @Override
   public QueryCouponsRespDTO listQueryUserCouponsByThread(QueryCouponsReqDTO requestParam) {
      //1.从redis中查询到用户优惠劵
      Set<String> rangeUserCoupons = stringRedisTemplate.opsForZSet().range(String.format(USER_COUPON_TEMPLATE_LIST_KEY, UserContext.getUserId()), 0, -1);
      //2.取出couponTemplateId并转换为key
      List<String> couponTemplateIds = rangeUserCoupons.stream()
              .map(each -> StrUtil.split(each, "_").get(0))
              .map(each -> String.format(COUPON_TEMPLATE_KEY, each))
              .toList();
      //3.Redis Pipeline 批量获取数据
      List<Object> couponTemplateList = stringRedisTemplate.executePipelined((RedisCallback<String>) connection -> {
         couponTemplateIds.forEach(each -> connection.hashCommands().hGetAll(each.getBytes()));
         return null;
      });
      //4.根据是否含有goods分为两个列表
      List<CouponTemplateQueryRespDTO> couponTemplateDTOList = JSON.parseArray(JSON.toJSONString(couponTemplateList), CouponTemplateQueryRespDTO.class);
      Map<Boolean, List<CouponTemplateQueryRespDTO>> partitioned = couponTemplateDTOList.stream()
              .collect(Collectors.partitioningBy(coupon -> StrUtil.isEmpty(coupon.getGoods())));
      List<CouponTemplateQueryRespDTO> goodEmptyList = partitioned.get(true);
      List<CouponTemplateQueryRespDTO> goodsNotEmptyList = partitioned.get(false);
      // 针对当前订单可用/不可用的优惠券列表
      /**
       * 为什么使用 Collections.synchronizedList
       * 由于我们将计算金额和判定优惠券可用性的逻辑进行了抽象封装，因此在并发场景下，多个线程可能会同时向同一个集合中添加数据，从而引发线程安全问题。
       * 为了解决这个问题，我们使用了 Collections.synchronizedList 方法，将集合包装为线程安全的版本，从而保证在并发修改集合时不会发生数据不一致或异常的情况。
       */
      List<QueryCouponsDetailRespDTO> availableCouponList = Collections.synchronizedList(new ArrayList<>());
      List<QueryCouponsDetailRespDTO> notAvailableCouponList = Collections.synchronizedList(new ArrayList<>());
      //5.并行处理 goodsEmptyList
      CompletableFuture<Void> emptyGoodsTasks=CompletableFuture.allOf(
              goodEmptyList.stream()
                      .map(each->CompletableFuture.runAsync(()->{
                         QueryCouponsDetailRespDTO resultCouponDeatail = BeanUtil.toBean(each, QueryCouponsDetailRespDTO.class);
                         JSONObject jsonObject = JSONObject.parseObject(each.getConsumeRule());
                         handleCouponLogic(resultCouponDeatail,jsonObject,requestParam.getOrderAmount(),availableCouponList,notAvailableCouponList);
                      },executorService))
                      .toArray(CompletableFuture[]::new)
      );
      //6.并行处理goodsNotEmptyList
      //将请求参数商品列表一一映射为：getGoodsNumber-QueryCouponGoodsReqDTO
      Map<String, QueryCouponGoodsReqDTO> goodsRequestMap = requestParam.getGoodsList().stream()
              .collect(Collectors.toMap(QueryCouponGoodsReqDTO::getGoodsNumber, Function.identity()));
      CompletableFuture<Void> notEmptyGoodsTasks=CompletableFuture.allOf(
              goodsNotEmptyList.stream()
              .map(each->CompletableFuture.runAsync(()->{
                 QueryCouponsDetailRespDTO resultCouponDetail = BeanUtil.toBean(each, QueryCouponsDetailRespDTO.class);
                 QueryCouponGoodsReqDTO couponGoods = goodsRequestMap.get(each.getGoods());
                 if (couponGoods==null){
                    notAvailableCouponList.add(resultCouponDetail);
                 }else{
                    JSONObject jsonObject = JSON.parseObject(each.getConsumeRule());
                    handleCouponLogic(resultCouponDetail,jsonObject,requestParam.getOrderAmount(),availableCouponList,notAvailableCouponList);
                 }
              },executorService))
                      .toArray(CompletableFuture[]::new)
      );
      //7.等待两个异步任务集合完成
      CompletableFuture.allOf(emptyGoodsTasks,notEmptyGoodsTasks)
              .thenRun(()->{
                 // 与业内标准一致，按最终优惠力度从大到小排序
                 availableCouponList.sort((c1, c2) -> c2.getCouponAmount().compareTo(c1.getCouponAmount()));
              })
              .join();
      return QueryCouponsRespDTO.builder()
              .availableCouponList(availableCouponList)
              .notAvailableCouponList(notAvailableCouponList)
              .build();

   }

   private void handleCouponLogic(QueryCouponsDetailRespDTO resultCouponDetail, JSONObject jsonObject, BigDecimal orderAmount, List<QueryCouponsDetailRespDTO> availableCouponList, List<QueryCouponsDetailRespDTO> notAvailableCouponList) {
      BigDecimal termsOfUse = jsonObject.getBigDecimal("termsOfUse");
      BigDecimal maximumDiscountAmount = jsonObject.getBigDecimal("maximumDiscountAmount");

      switch (resultCouponDetail.getType()) {
         case 0: // 立减券
            resultCouponDetail.setCouponAmount(maximumDiscountAmount);
            availableCouponList.add(resultCouponDetail);
            break;
         case 1: // 满减券
            if (orderAmount.compareTo(termsOfUse) >= 0) {
               resultCouponDetail.setCouponAmount(maximumDiscountAmount);
               availableCouponList.add(resultCouponDetail);
            } else {
               notAvailableCouponList.add(resultCouponDetail);
            }
            break;
         case 2: // 折扣券
            if (orderAmount.compareTo(termsOfUse) >= 0) {
               BigDecimal discountRate = jsonObject.getBigDecimal("discountRate");
               BigDecimal multiply = orderAmount.multiply(discountRate);
               if (multiply.compareTo(maximumDiscountAmount) >= 0) {
                  resultCouponDetail.setCouponAmount(maximumDiscountAmount);
               } else {
                  resultCouponDetail.setCouponAmount(multiply);
               }
               availableCouponList.add(resultCouponDetail);
            } else {
               notAvailableCouponList.add(resultCouponDetail);
            }
            break;
         default:
            throw new ClientException("无效的优惠券类型");
      }
   }
}
